In [ ]:
# Setup environment
import numpy as np
import pandas as pd
import pandas_datareader as pdr
from pandas.plotting import register_matplotlib_converters
register_matplotlib_converters()
import matplotlib.pyplot as plt
import seaborn as sns
import datetime as dt
import ta
import yfinance as yf
import warnings
warnings.filterwarnings('ignore')
In [3]:
# Get data
tickers = 'AMZN GOOG NFLX META GLD'.split()
tickers
Out[3]:
['AMZN', 'GOOG', 'NFLX', 'META', 'GLD']
In [4]:
# Fetch historical market data
start = dt.datetime(2024, 1, 1)
end = dt.datetime(2025, 12, 31)
data = yf.download(tickers, start=start, end=end, auto_adjust=True)['Close']
data
[*********************100%***********************]  5 of 5 completed
Out[4]:
Ticker AMZN GLD GOOG META NFLX
Date
2024-01-02 149.929993 190.720001 138.611435 344.168335 468.500000
2024-01-03 148.470001 189.130005 139.406006 342.359497 470.260010
2024-01-04 144.570007 189.320007 137.101776 344.993195 474.670013
2024-01-05 145.240005 189.350006 136.456177 349.793640 474.059998
2024-01-08 149.100006 187.869995 139.574860 356.462524 485.029999
... ... ... ... ... ...
2025-09-25 218.149994 344.750000 246.570007 748.909973 1208.239990
2025-09-26 219.779999 346.739990 247.179993 743.750000 1210.609985
2025-09-29 222.169998 352.459991 244.360001 743.400024 1206.410034
2025-09-30 219.570007 355.470001 243.550003 734.380005 1198.920044
2025-10-01 220.630005 356.029999 245.539993 717.340027 1170.900024

439 rows × 5 columns

In [5]:
# Basic data description statistics
print(data.describe())
print(data.info())
Ticker        AMZN         GLD        GOOG        META         NFLX
count   439.000000  439.000000  439.000000  439.000000   439.000000
mean    197.090752  253.096446  173.776613  576.305771   855.730500
std      22.643252   43.713753   23.327895  105.307353   246.906023
min     144.570007  184.419998  131.659012  342.359497   468.500000
25%     180.055000  218.040001  159.663727  495.684937   634.015015
50%     192.169998  245.000000  170.075638  570.635681   823.960022
75%     219.375000  298.110001  185.015808  655.525665  1099.200012
max     242.059998  356.029999  255.240005  789.467163  1339.130005
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 439 entries, 2024-01-02 to 2025-10-01
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   AMZN    439 non-null    float64
 1   GLD     439 non-null    float64
 2   GOOG    439 non-null    float64
 3   META    439 non-null    float64
 4   NFLX    439 non-null    float64
dtypes: float64(5)
memory usage: 20.6 KB
None
In [6]:
# Plot closing prices
data.plot(figsize=(10, 6))
plt.title('Closing Prices of Selected Assets')
# Display the ticker symbols and last closing prices in plot
for i, ticker in enumerate(tickers):
    plt.text(data.index[-1], data[ticker].iloc[-1], f'{ticker}: {data[ticker].iloc[-1]:.2f}', fontsize=9, verticalalignment='center')
plt.xlabel('Date')
plt.ylabel('Price')
plt.legend()
plt.show()
No description has been provided for this image
In [7]:
# Normalized prices
normalized_data = data / data.iloc[0] * 100
normalized_data.plot(figsize=(10, 6))
plt.title('Normalized Closing Prices of Stock (Base 100)')
for i, ticker in enumerate(tickers):
    plt.text(data.index[-1], normalized_data[ticker].iloc[-1], f'{ticker}: {normalized_data[ticker].iloc[-1]:.2f}', fontsize=9, verticalalignment='center')
plt.xlabel('Date')
plt.ylabel('Normalized Price (Base 100)')
plt.legend()
plt.show()
No description has been provided for this image
In [8]:
# Calculate log returns, about monetary returns need to compound the interests.
pct_change = np.log1p(data).diff().dropna()
pct_change
Out[8]:
Ticker AMZN GLD GOOG META NFLX
Date
2024-01-03 -0.009720 -0.008328 0.005675 -0.005254 0.003742
2024-01-04 -0.026439 0.000999 -0.016547 0.007641 0.009314
2024-01-05 0.004592 0.000158 -0.004686 0.013779 -0.001283
2024-01-08 0.026053 -0.007806 0.022435 0.018832 0.022829
2024-01-09 0.015010 0.000318 0.014241 -0.003426 -0.006067
... ... ... ... ... ...
2025-09-25 -0.009356 0.004144 -0.005077 -0.015547 0.003554
2025-09-26 0.007410 0.005739 0.002461 -0.006905 0.001958
2025-09-29 0.010767 0.016315 -0.011428 -0.000470 -0.003472
2025-09-30 -0.011719 0.008480 -0.003307 -0.012191 -0.006223
2025-10-01 0.004794 0.001570 0.008104 -0.023444 -0.023629

438 rows × 5 columns

In [9]:
# Actual percentage change
changed = data.pct_change().dropna()
changed
Out[9]:
Ticker AMZN GLD GOOG META NFLX
Date
2024-01-03 -0.009738 -0.008337 0.005732 -0.005256 0.003757
2024-01-04 -0.026268 0.001005 -0.016529 0.007693 0.009378
2024-01-05 0.004634 0.000158 -0.004709 0.013915 -0.001285
2024-01-08 0.026577 -0.007816 0.022855 0.019065 0.023141
2024-01-09 0.015225 0.000319 0.014445 -0.003430 -0.006061
... ... ... ... ... ...
2025-09-25 -0.009355 0.004165 -0.005084 -0.015447 0.003563
2025-09-26 0.007472 0.005772 0.002474 -0.006890 0.001962
2025-09-29 0.010875 0.016497 -0.011409 -0.000471 -0.003469
2025-09-30 -0.011703 0.008540 -0.003315 -0.012133 -0.006208
2025-10-01 0.004828 0.001575 0.008171 -0.023203 -0.023371

438 rows × 5 columns

In [10]:
# Describe percentage change
changed.describe()
Out[10]:
Ticker AMZN GLD GOOG META NFLX
count 438.000000 438.000000 438.000000 438.000000 438.000000
mean 0.001075 0.001479 0.001483 0.001953 0.002286
std 0.019661 0.010291 0.018866 0.023785 0.019702
min -0.089791 -0.035683 -0.075061 -0.105613 -0.090933
25% -0.009291 -0.004563 -0.008674 -0.009626 -0.007303
50% 0.000423 0.001918 0.003144 0.001087 0.002076
75% 0.012161 0.008190 0.011533 0.012064 0.013201
max 0.119770 0.036991 0.099652 0.203176 0.110870
In [11]:
# Plot histogram of daily returns
plt.figure(figsize=(10, 6))
sns.histplot(changed, bins=50, kde=True, alpha=0.5)
plt.title('Histogram of Daily Returns')
plt.xlabel('Daily Return')
plt.ylabel('Frequency')
plt.show()
No description has been provided for this image
In [12]:
# Pairplot of daily returns
sns.pairplot(changed)
plt.suptitle('Pairplot of Daily Returns', y=1.02)
plt.show()
No description has been provided for this image
In [13]:
# Correlation heatmap
plt.figure(figsize=(8, 6))
sns.heatmap(changed.corr(), annot=True, cmap='coolwarm', fmt=".2f", vmin=-1, vmax=1, center=0, square=True, cbar_kws={"shrink": .8})
plt.title('Correlation Heatmap of Daily Returns')
plt.show()
No description has been provided for this image

Techincal Analysis of Stock Prices

In [14]:
# Moving averages in 1 month and 3 months

mo_21 = data.rolling(window=21).mean() # 21 trading days in a months
mo_63 = data.rolling(window=63).mean() # 63 trading days in 3 months
for ticker in tickers:
    plt.rcParams['figure.figsize'] = (14, 7)
    plt.plot(data.index, data[ticker], label=ticker, color='blue')
    plt.plot(mo_21.index, mo_21[ticker], label='21-Day MA', color='orange')
    plt.plot(mo_63.index, mo_63[ticker], label='63-Day MA', color='red')
    plt.title(f'{ticker} Price with 21-Day and 63-Day Moving Averages')
    plt.xlabel('Date')
    plt.ylabel('Price')
    plt.legend()
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Bollinger Bands - A volatility indicator that consists of a moving average and two standard deviation lines above and below it.

In [15]:
# The standard deviation in 21 days
std_21 = data.rolling(window=21).std()
upper_band = mo_21 + (std_21 * 2)
lower_band = mo_21 - (std_21 * 2)
for ticker in tickers:
    plt.figure(figsize=(14, 7))
    plt.plot(data.index, data[ticker], label=ticker, color='blue')
    plt.plot(mo_21.index, mo_21[ticker], label='21-Day MA', color='orange')
    plt.plot(upper_band.index, upper_band[ticker], label='Upper Bollinger Band', color='green', linestyle='--')
    plt.plot(lower_band.index, lower_band[ticker], label='Lower Bollinger Band', color='red', linestyle='--')
    plt.fill_between(data.index, lower_band[ticker], upper_band[ticker], color='grey', alpha=0.1)
    plt.title(f'{ticker} Price with Bollinger Bands')
    plt.xlabel('Date')
    plt.ylabel('Price')
    plt.legend()
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

RSI (Relative Strength Index) - A momentum oscillator that measures the speed and change of price movements, typically used to identify overbought or oversold conditions.

In [16]:
# Momentum (RSI) for 14 days for each ticker
momentum_14 = pd.DataFrame({ticker: ta.momentum.rsi(data[ticker], window=14) for ticker in data.columns})
momentum_14.dropna()
# Plot RSI for each ticker
for ticker in tickers:
    fig, ax = plt.subplots(2,1, figsize=(14, 10))
    plt.subplot(2,1,1)
    plt.plot(data.index, data[ticker], label=ticker, color='blue')
    plt.title(f'{ticker} Closing Price')
    plt.xlabel('Date')
    plt.ylabel('Price')
    plt.legend()
    plt.subplot(2,1,2)
    plt.plot(momentum_14.index, momentum_14[ticker], label=f'{ticker} RSI (14 days)', color='purple')
    plt.axhline(70, color='red', linestyle='--', label='Overbought (70)')
    plt.axhline(30, color='green', linestyle='--', label='Oversold (30)')
    plt.title(f'{ticker} Relative Strength Index (RSI) - 14 Days')
    plt.xlabel('Date')
    plt.ylabel('RSI')
    plt.legend()
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [17]:
# MACD for each ticker
macd = pd.DataFrame({ticker: ta.trend.macd(data[ticker]) for ticker in data.columns})
macd_signal = pd.DataFrame({ticker: ta.trend.macd_signal(data[ticker]) for ticker in data.columns})
macd_diff = pd.DataFrame({ticker: ta.trend.macd_diff(data[ticker]) for ticker in data.columns})
# Plot MACD for each ticker
for ticker in tickers:
    fig, ax = plt.subplots(2,1, figsize=(14, 10))
    plt.subplot(2,1,1)
    plt.plot(data.index, data[ticker], label=ticker, color='blue')
    plt.title(f'{ticker} Closing Price')
    plt.xlabel('Date')
    plt.ylabel('Price')
    plt.legend()
    plt.subplot(2,1,2)
    plt.plot(macd.index, macd[ticker], label='MACD', color='purple')
    plt.plot(macd_signal.index, macd_signal[ticker], label='Signal Line', color='orange')
    plt.bar(macd_diff.index, macd_diff[ticker], label='MACD Histogram', color='grey', alpha=0.5)
    plt.title(f'{ticker} MACD')
    plt.xlabel('Date')
    plt.ylabel('MACD Value')
    plt.legend()
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [22]:
# Download OHLC data for ADX calculation
ohlc_data = yf.download(tickers, start=start, end=end, auto_adjust=True)[['High', 'Low', 'Close']]

# Calculate ADX for each ticker
adx = pd.DataFrame({
    ticker: ta.trend.adx(
        ohlc_data['High'][ticker],
        ohlc_data['Low'][ticker],
        ohlc_data['Close'][ticker],
        window=14
    )
    for ticker in tickers
})

# Plot ADX for each ticker
for ticker in tickers:
    fig, ax1 = plt.subplots(figsize=(14, 8))
    ax1.plot(data.index, data[ticker], label=ticker, color='blue')
    ax1.set_xlabel('Date')
    ax1.set_ylabel('Price')
    #ax1.set_title(f'{ticker} Closing Price')
    ax1.legend(loc='upper right')

    ax2 = ax1.twinx()
    ax2.plot(adx.index, adx[ticker], label='ADX', color='red')
    ax2.axhline(25, color='green', linestyle='--', label='Strong Trend Threshold (25)')
    ax2.set_ylabel('ADX Value')
    ax2.set_title(f'{ticker} Average Directional Index (ADX) - 14 Days')
    ax2.legend(loc='upper left')

    plt.show()
[*********************100%***********************]  5 of 5 completed
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [23]:
# Stochastic Oscillator for each ticker
stoch_k = pd.DataFrame({ticker: ta.momentum.stoch(ohlc_data['High'][ticker], ohlc_data['Low'][ticker], ohlc_data['Close'][ticker], window=14, smooth_window=3) for ticker in tickers})
stoch_d = pd.DataFrame({ticker: ta.momentum.stoch_signal(ohlc_data['High'][ticker], ohlc_data['Low'][ticker], ohlc_data['Close'][ticker], window=14, smooth_window=3) for ticker in tickers})
# Plot Stochastic Oscillator for each ticker
for ticker in tickers:
    fig, ax1 = plt.subplots(figsize=(14, 7))
    ax1.plot(data.index, data[ticker], label=ticker, color='blue')
    ax1.set_xlabel('Date')
    ax1.set_ylabel('Price')
    #ax1.set_title(f'{ticker} Closing Price')
    ax1.legend(loc='upper right')

    ax2 = ax1.twinx()
    ax2.plot(stoch_k.index, stoch_k[ticker], label='%K', color='purple')
    ax2.plot(stoch_d.index, stoch_d[ticker], label='%D', color='orange')
    ax2.axhline(80, color='red', linestyle='--', label='Overbought (80)')
    ax2.axhline(20, color='green', linestyle='--', label='Oversold (20)')
    ax2.set_ylabel('Stochastic Oscillator Value')
    ax2.set_title(f'{ticker} Stochastic Oscillator - 14 Days')
    ax2.legend(loc='upper left')

    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [ ]: